
#include "stdafx.h"
#include "tvec.h" //for atexit handling

//from C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\crt\src\internal.h:
typedef void (__cdecl *_PVFV)(void);
typedef int  (__cdecl *_PIFV)(void);
typedef void (__cdecl *_PVFI)(int);

//from C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\crt\src\sect_attribs.h:
#define _CRTALLOC(x) __declspec(allocate(x))

//predefined linker sections for C and C++ initialisation code
#pragma section(".CRT$XIA",long,read)
#pragma section(".CRT$XIZ",long,read)
#pragma section(".CRT$XCA",long,read)
#pragma section(".CRT$XCZ",long,read)


//using heap to ensure there is enough space for all atexit calls
class TcrtAllocator
{
public:
	static void* allocate(size_t n) { return HeapAlloc(GetProcessHeap(), 0, n); }
	static void deallocate(void* p) { HeapFree(GetProcessHeap(), 0, p); }
};

//since the exit stack is type has a dtor, can't declare a static instance of it directly here.
//it would end up in the dynamic dtor list it is maintaining, which would lead to hilarity.
//instead, it is put in an appropiate scope such that it lives and dies on the program stack.
typedef tstd::vector<_PVFV, TcrtAllocator> atexit_stack_t;
static atexit_stack_t* atexit_stack = NULL;

//declare the relevant crt initialiser function lists and implement atexit
extern "C"
{
	//this symbol is provided by the linker
	extern IMAGE_DOS_HEADER __ImageBase;

	_CRTALLOC(".CRT$XIA") _PIFV __xi_a[] = {NULL};
	_CRTALLOC(".CRT$XIZ") _PIFV __xi_z[] = {NULL};   /* C initializers */
	_CRTALLOC(".CRT$XCA") _PVFV __xc_a[] = {NULL};
	_CRTALLOC(".CRT$XCZ") _PVFV __xc_z[] = {NULL};   /* C++ initializers */

	int atexit(_PVFV f)
	{
		//ignore empty calls
		if(f == 0)
			return 0;

		atexit_stack->push_back(f);
		return 0; //errorz!
	}

	void doatexit()
	{
		//traverse stack backwards...
		size_t l = atexit_stack->size();
		for(size_t i=0; i<l; ++i)
		{
			(*atexit_stack)[l-i-1]();
		}			
	}

	//symbol used by linker to force fp support to load (or not, in our case)
	int _fltused=0;
	//another symbol used for conditional processing in ftol2.obj
	int __sse2_available=0;

	//stub pure virtuals are linked to, should never be run
	int _purecall() { return 0; }

	//provide a memcpy routine, required by the vector implementation used by tcrt
	//to implement heap-bound atexit stack sizes.
	void* memcpy(void* pDst, const void* pSrc, size_t sz)
	{
		if(pDst == pSrc)
			return pDst;

		//generic byte-by-byte copy (slow!)
		char* cdst = reinterpret_cast<char*>(pDst);
		const char* csrc = reinterpret_cast<const char*>(pSrc);
		for(size_t i=0; i<sz; ++i)
		{
			*(cdst+i) = *(csrc+i);
		}
		return pDst;
	}

	//and a few more common ones that have come in handy over time...
	void* memset(void* pDst, int val, size_t size)
	{
		unsigned char v = static_cast<unsigned char>(val);
		unsigned char* p = reinterpret_cast<unsigned char*>(pDst);
		for(size_t i=0; i<size; ++i, ++p)
			*p = v;		
		return pDst;
	}

	size_t strlen(const char* str)
	{
		const char* p = str;
		while(*p) { ++p; }
		return p - str;
	}

	int strcmp(const char* str1, const char* str2)
	{
		while(*str1 && *str2)
		{
			if(*str1 != *str2)
				return (*str1 - *str2);
			++str1;
			++str2;
		}
		return (*str1 - *str2);
	}

	//from C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\include\vadefs.h
	#define _ADDRESSOF(v)   ( &reinterpret_cast<const char &>(v) )
	#define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )

	#define _crt_va_start(ap,v)  ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )
	#define _crt_va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
	#define _crt_va_end(ap)      ( ap = (va_list)0 )

	typedef char *  va_list;

	//from C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\include\stdargs.h
	#define va_start _crt_va_start
	#define va_arg _crt_va_arg
	#define va_end _crt_va_end

	int sprintf(char* buff, const char* format, ... )
	{
		va_list args;
		va_start(args, format);

		int r = wvsprintfA(buff, format, args);

		va_end(args);
		return r;
	}

/*		typedef struct __JUMP_BUFFER {
		unsigned long Ebp;
		unsigned long Ebx;				//4
		unsigned long Edi;				//8
		unsigned long Esi;				//12 - 0Ch
		unsigned long Esp;				//16 - 10h
		unsigned long Eip;				//20 - 14h
		unsigned long Registration;		//24 - 18h
		unsigned long TryLevel;			//28 - 1ch
		unsigned long Cookie;			//32 - 20h
		unsigned long UnwindFunc;		//36 - 24h
		unsigned long UnwindData[6];	//40
	} JumpBuffer;
*/

	//setjmp is implemented in tandem with a compiler intrinsic. the compiler may have pushed extra dwords
	//to the stack, which are related to C++ exception unwinding and beyond what this can handle.
	//this function is declspec(naked), so we have to provide the stack management.
	//in contrary to normal practice, we don't move esp or ebp. there are no locals used,
	//and we want to capture the calling stack environment - not the stack environment inside this function!
	int __declspec(naked) _setjmp3( jmp_buf env, int count )
	{
		//nb: not pushing a stack frame, which breaks the inline assemblers
		//automatic calculation of stack address for parameters - so they are
		//found by offset from the stack pointer.
		_asm
		{
			cmp		[esp+8], 0
			je		no_cpp_exceptions
			int		3						//stop, in the crap. C++ exception unwinding needed.
		no_cpp_exceptions:
			mov		edx		, [esp+4]
			mov		[edx]	, ebp;
			mov		[edx+4]	, ebx;
			mov		[edx+8]	, edi;
			mov		[edx+12], esi;
			mov		[edx+16], esp;
			mov		eax		, [esp];
			mov		[edx+20], eax;
			mov		eax		, FS:[0];
			mov		[edx+24], eax;
			mov		[edx+28], 0;
			mov		[edx+32], 0x56433230u;	//todo: this only writes one byte! (use own cookie?)
			mov		[edx+36], 0;
			xor		eax, eax	//clear eax - return 0
			ret
		}
	}

	//(this function could be naked too, but it doesn't make any difference in the end)
	__declspec(noreturn) void __cdecl _longjmp(jmp_buf env, int value)
	{
		//if registration has changed, need to unwind SEH frames. gawd knows...
		//alter stack to point back to return address of setjmp call
		//restore registers
		//push value in eax and return.
		_asm
		{
			mov		eax , FS:[0];
			mov		edx	, [env];
			cmp		eax , [edx+24];	//has the SEH frame changed?
			je		jumpback;
			//todo: fix SEH frames
			int		3;				//stop, in the crap. Need to invoke windows SEH unwind back to saved Registration
		jumpback:
			mov		eax , value		//move value to eax (return reg)
			cmp		eax, 0			//make sure return value isn't 0
			jne		value_ok
			mov		eax, 1
		value_ok:
			mov		ebx , [edx+4]	//restore all registers
			mov		edi , [edx+8]	
			mov		esi , [edx+12]
			mov		esp , [edx+16]
			add		esp , 4			//pop the return address
			mov		ebp , [edx+0]	//restore the frame pointer

			jmp		dword ptr [edx+20]	//this is it - time to jump back to return address
		}
	}

};

//run a table (no error checking for the _PIFV types)
template<class T> 
void runtable(T* a, T* z)
{
	T* c = a;
	while(c < z)
	{
		if(*c)
		{ (**c)(); }

		++c;
	}
}

void DynamicInit()
{
	runtable( __xi_a, __xi_z ); //C
	runtable( __xc_a, __xc_z ); //C++
}

void DynamicDtor()
{
	//deliberately empty, not running any of the dtor tables yet.
	//doatexit() is called seperately, and handles C++ shutdown.
}

//declare our expectations of the user main function - winmain prototype requires
//least processing (no cmd line parse)
extern int __stdcall wmain(HINSTANCE hInstance, LPTSTR lpCommandLine, int nCmdShow);

//application entry point - raw, need to do own CRT and C++ setup.
int enter()
{
	//startup info, this can be used to simulate a winmain and to setup stdout, stderr, stdin
	STARTUPINFO si;
	GetStartupInfoW(&si);

	//module handle also required for a winmain, linker has provided the instance data.
	HINSTANCE h = (HINSTANCE)&__ImageBase;

	//todo: use the contents of STARTUPINFO to initialise e.g. stdout

	int r = 0;
	{
		//dynamic init populates the atexit for all statics, so create the stack here
		atexit_stack_t atexit_fns(20);
		atexit_stack = &atexit_fns;

		//run initialisers
		DynamicInit();

		//trigger the application entry point (aka main)
		r = wmain(h, GetCommandLine(), si.wShowWindow);

		//run shutdown
		DynamicDtor();
		doatexit();

		//atexit stack goes out of scope now, releasing memory before ExitProcess. hooray!
	}

	//return code from user main
	ExitProcess(r);
}
